#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
HygonJDK 调优脚本: 上下文切换分析 (第 5.2 节)
用法:
  # 1. (在服务器上) 采集数据:
  # sudo perf record -e sched:sched_switch -a sleep 30
  # 2. (在服务器上) 导出脚本:
  # sudo perf script -i perf.data > summary.txt
  # 3. (本地或服务器) 分析:
  # cat summary.txt | python3 analyze_context_switch.py
  python3 analyze_context_switch.py summary.txt
"""

import re
import sys
from collections import defaultdict, Counter

# 示例:
# Grizzly-worker( 1708092 [005] 277435.745490: sched:sched_switch: Grizzly-worker(:1708092 [120] S ==> swapper/5:0 [120]
RE_SCHED_SWITCH = re.compile(
    r'(\S+)\s+(\d+)\s+\[(\d+)\]\s+[\d.]+\:\s+sched:sched_switch:\s+'
    r'(\S+):(\d+)\s+\[[\d]+\]\s+(\w)\s+==>\s+(\S+):(\d+)'
)


def parse_perf_log(log_content):
    analysis = {
        "switches_per_core": Counter(),
        "switches_per_thread": Counter(),
        "voluntary_switches": Counter(),      # 主动切换 (S, D)
        "involuntary_switches": Counter(),    # 被动切换 (R)
        "total_switches": 0,
        "lines_parsed": 0
    }

    for line in log_content.splitlines():
        match = RE_SCHED_SWITCH.search(line)
        if not match:
            continue

        analysis["lines_parsed"] += 1
        
        prev_comm = match.group(1)
        prev_pid = match.group(2)
        cpu_id = match.group(3)
        prev_state = match.group(6)
        
        prev_thread_name = f"{prev_comm.strip()}({prev_pid})"

        analysis["total_switches"] += 1
        analysis["switches_per_core"][cpu_id] += 1
        analysis["switches_per_thread"][prev_thread_name] += 1

        # 分析切换类型
        if prev_state in ('S', 'D'):
            analysis["voluntary_switches"][prev_thread_name] += 1
        elif prev_state == 'R':
            analysis["involuntary_switches"][prev_thread_name] += 1
            
    return analysis

def print_perf_report(analysis):
    print("=" * 70)
    print("HygonJDK 调优助手 - 上下文切换分析报告")
    print("=" * 70)
    
    if analysis["total_switches"] == 0:
        print("[\033[91m错误\033[0m] 未能从日志中解析出 sched:sched_switch 事件。")
        print("请确保日志是由 'perf script' 生成的。")
        print("=" * 70)
        return

    print(f"总计: 解析了 {analysis['lines_parsed']} 行, 识别出 {analysis['total_switches']} 次切换事件。")

    # 1. 核心切换统计 (Top 10)
    print("\n--- 1. 核心(CPU)切换次数 (Top 10) ---")
    sorted_cores = analysis["switches_per_core"].most_common(10)
    for core, count in sorted_cores:
        print(f"  - CPU[{core:<3}]: {count} 次切换")

    # 2. 线程切换统计 (Top 10)
    print("\n--- 2. 线程切换次数 (Top 10) ---")
    sorted_threads = analysis["switches_per_thread"].most_common(10)
    for thread, count in sorted_threads:
        print(f"  - {thread:<40}: {count} 次切换")

    # 3. 被动切换 (时间片用尽/抢占) (Top 10)
    print("\n--- 3. \033[91m被动切换\033[0m (prev_state=R) 排行 (Top 10) ---")
    print(" (这表明线程想继续运行, 但被强制调度, 可能是CPU资源不足)")
    sorted_involuntary = analysis["involuntary_switches"].most_common(10)
    if not sorted_involuntary:
        print("  [\033[92m正常\033[0m] 未检测到被动切换。")
    for thread, count in sorted_involuntary:
        total = analysis['switches_per_thread'][thread]
        print(f"  - {thread:<40}: {count} 次 (占该线程总切换的 {(count/total*100):.1f}%)")

    # 4. 主动切换 (I/O, sleep, wait) (Top 10)
    print("\n--- 4. \033[93m主动切换\033[0m (prev_state=S/D) 排行 (Top 10) ---")
    print(" (这表明线程在等待 I/O, 锁, 或 sleep)")
    sorted_voluntary = analysis["voluntary_switches"].most_common(10)
    if not sorted_voluntary:
        print("  [\033[94m信息\033[0m] 未检测到主动切换。")
    for thread, count in sorted_voluntary:
        total = analysis['switches_per_thread'][thread]
        print(f"  - {thread:<40}: {count} 次 (占该线程总切换的 {(count/total*100):.1f}%)")

    print("=" * 70)

def main():
    try:
        if len(sys.argv) > 1:
            filepath = sys.argv[1]
            with open(filepath, 'r', encoding='utf-8') as f:
                log_content = f.read()
        else:
            log_content = sys.stdin.read()
            
        if not log_content:
            print("错误: 未提供日志内容。", file=sys.stderr)
            print(__doc__, file=sys.stderr)
            sys.exit(1)

        analysis = parse_perf_log(log_content)
        print_perf_report(analysis)

    except FileNotFoundError:
        print(f"错误: 文件未找到 {sys.argv[1]}", file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"发生错误: {e}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()
